// Date:   Tue Aug 12 04:25:25 PM 2025
// Mail:   lunar_ubuntu@qq.com
// Author: https://github.com/xiaoqixian

#pragma once
#include <vector>
#include <stdexcept>
#include <utility>
#include <cstddef>

template<typename T>
class RingBuffer {
public:
  // Construct a ring buffer with fixed capacity (must be > 0)
  explicit RingBuffer(size_t capacity)
    : m_buf(capacity), m_capacity(capacity), m_head(0), m_tail(0), m_size(0) {
    if (capacity == 0) throw std::invalid_argument("capacity must be > 0");
  }

  // Disable copy if you want, but here we keep copy semantics
  RingBuffer(const RingBuffer& other) = default;
  RingBuffer& operator=(const RingBuffer& other) = default;

  RingBuffer(RingBuffer&& other) noexcept = default;
  RingBuffer& operator=(RingBuffer&& other) noexcept = default;

  ~RingBuffer() = default;

  // Return capacity
  size_t capacity() const noexcept { return m_capacity; }

  // Return current size
  size_t size() const noexcept { return m_size; }

  bool empty() const noexcept { return m_size == 0; }
  bool full() const noexcept { return m_size == m_capacity; }

  // Push element, throws std::overflow_error if full
  void push(const T& value) {
    if (full()) throw std::overflow_error("RingBuffer is full");
    m_buf[m_tail] = value;
    advance_tail();
  }

  void push(T&& value) {
    if (full()) throw std::overflow_error("RingBuffer is full");
    m_buf[m_tail] = std::move(value);
    advance_tail();
  }

  // Emplace construct in-place, throws if full
  template<typename... Args>
  void emplace(Args&&... args) {
    if (full()) throw std::overflow_error("RingBuffer is full");
    m_buf[m_tail] = T(std::forward<Args>(args)...);
    advance_tail();
  }

  // Try push without throwing; returns false if full
  bool try_push(const T& value) noexcept {
    if (full()) return false;
    m_buf[m_tail] = value;
    advance_tail();
    return true;
  }

  bool try_push(T&& value) noexcept {
    if (full()) return false;
    m_buf[m_tail] = std::move(value);
    advance_tail();
    return true;
  }

  // Pop front element, throws std::underflow_error if empty
  T pop() {
    if (empty()) throw std::underflow_error("RingBuffer is empty");
    T out = std::move(m_buf[m_head]);
    advance_head();
    return out;
  }

  // Try pop without throwing; returns false if empty
  bool try_pop(T& out) noexcept {
    if (empty()) return false;
    out = std::move(m_buf[m_head]);
    advance_head();
    return true;
  }

  // Access front/back, throws if empty
  T& front() {
    if (empty()) throw std::underflow_error("RingBuffer is empty");
    return m_buf[m_head];
  }
  const T& front() const {
    if (empty()) throw std::underflow_error("RingBuffer is empty");
    return m_buf[m_head];
  }

  T& back() {
    if (empty()) throw std::underflow_error("RingBuffer is empty");
    size_t idx = (m_tail == 0) ? (m_capacity - 1) : (m_tail - 1);
    return m_buf[idx];
  }
  const T& back() const {
    if (empty()) throw std::underflow_error("RingBuffer is empty");
    size_t idx = (m_tail == 0) ? (m_capacity - 1) : (m_tail - 1);
    return m_buf[idx];
  }

  // Random access by index in [0, size()). 0 -> front()
  T& operator[](size_t index) {
    if (index >= m_size) throw std::out_of_range("index out of range");
    size_t idx = (m_head + index) % m_capacity;
    return m_buf[idx];
  }
  const T& operator[](size_t index) const {
    if (index >= m_size) throw std::out_of_range("index out of range");
    size_t idx = (m_head + index) % m_capacity;
    return m_buf[idx];
  }

  // Clear buffer (destroy elements if needed)
  void clear() noexcept {
    m_head = m_tail = m_size = 0;
  }

  // Overwrite push: if full, drop oldest element and push new one
  void push_overwrite(const T& value) noexcept {
    if (full()) {
      m_buf[m_tail] = value;
      advance_tail();
      m_head = m_tail; // oldest is overwritten, move head to next
    } else {
      push(value);
    }
  }
  void push_overwrite(T&& value) noexcept {
    if (full()) {
      m_buf[m_tail] = std::move(value);
      advance_tail();
      m_head = m_tail;
    } else {
      push(std::move(value));
    }
  }

private:
  // Advance tail after writing, and increment size
  void advance_tail() noexcept {
    m_tail = (m_tail + 1) % m_capacity;
    ++m_size;
  }

  // Advance head after reading, decrement size
  void advance_head() noexcept {
    m_head = (m_head + 1) % m_capacity;
    --m_size;
  }

private:
  std::vector<T> m_buf;   // underlying storage
  size_t m_capacity;      // fixed capacity
  size_t m_head;          // index of current front
  size_t m_tail;          // index of next write position
  size_t m_size;          // current number of elements
};
